﻿using System;
using System.Net;
using System.Net.Sockets;

namespace Comunication
{
    public class UDPClient
    {
        public static void BeginReceiveUDP(int port, IPAddress multicastIp, IUdpPacketProcessor packetProcessor)
        {
            try
            {
                IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Any, port);
                Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

                udpSocket.Bind(localEndpoint);

                udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastIp));
                udpSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);

                EndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);

                UDPSocketState udpSocketState = new UDPSocketState() { Buffer = new byte[1024], RemoteEndpoint = (IPEndPoint)remoteEndpoint, Socket = udpSocket, UdpPacketProcessor = packetProcessor };

                udpSocket.BeginReceiveFrom(udpSocketState.Buffer, 0, 1024, SocketFlags.None, ref remoteEndpoint, new AsyncCallback(UdpReceiveComplete),
                                           udpSocketState);
            }
            catch (ObjectDisposedException exception)
            {
                throw new Exception("Se ha perdido conexion con el socket", exception.InnerException);
            }
            catch (SocketException exception)
            {
                throw new Exception(exception.Message, exception.InnerException);
            }
            catch (Exception exception)
            {
                throw new Exception(exception.Message);
            }
        }

        private static void UdpReceiveComplete(IAsyncResult ar)
        {
            try
            {
                UDPSocketState udpSocketState = (UDPSocketState)ar.AsyncState;

                EndPoint remoteEndpoint = udpSocketState.RemoteEndpoint;
                int read = udpSocketState.Socket.EndReceiveFrom(ar, ref remoteEndpoint);

                byte[] buffer = udpSocketState.Buffer;
                udpSocketState.Buffer = new byte[1024];

                udpSocketState.RemoteEndpoint = new IPEndPoint(IPAddress.Any, 0);

                udpSocketState.Socket.BeginReceiveFrom(udpSocketState.Buffer, 0, 1024, SocketFlags.None, ref remoteEndpoint,
                                                    UdpReceiveComplete, udpSocketState);

                udpSocketState.UdpPacketProcessor.ProcessPacket(buffer, (IPEndPoint)remoteEndpoint);
            }
            catch (ObjectDisposedException exception)
            {
                throw new Exception("Se ha perdido conexion con el socket", exception.InnerException);
            }
            catch (SocketException exception)
            {
                throw new Exception(exception.Message, exception.InnerException);
            }
            catch (Exception exception)
            {
                throw new Exception(exception.Message);
            }
        }

        public static bool SendUdpMessage(IPEndPoint remoteEndpoint,Command command)
        {
            try
            {
                Socket udpsck = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                int cantBytes = udpsck.SendTo(command.ToByteArray(), SocketFlags.None, remoteEndpoint);
                return cantBytes == command.ToByteArray().Length;
            }
            catch (ObjectDisposedException exception)
            {
                throw new Exception("Se ha perdido conexion con el socket", exception.InnerException);
            }
            catch (SocketException exception)
            {
                throw new Exception(exception.Message, exception.InnerException);
            }
            catch (Exception exception)
            {
                throw new Exception(exception.Message);
            }
        }
    }
}